create or replace package body m4o_v2 as

  -- ===========================================================================
  function split(
    i_str       in varchar2,
    i_separator in varchar2 := ',')
  return m4o_v2tab
  is
    v_tbl        m4o_v2tab   := m4o_v2tab();
    v_start      pls_integer := 1;
    v_sep_length pls_integer := length(i_separator);
    v_sep_pos    pls_integer;
  begin
    IF i_str is null THEN return v_tbl; END IF;

    LOOP
      v_sep_pos := instr(i_str,i_separator,v_start);
      v_tbl.extend;
      IF v_sep_pos = 0 THEN
        v_tbl(v_tbl.count) := substr(i_str,v_start);
        exit;
      ELSE
        v_tbl(v_tbl.count) := substr(i_str,v_start,v_sep_pos-v_start);
	v_start := v_sep_pos + v_sep_length;
      END IF;
    END LOOP;

    return v_tbl;
  end;
  -- ===========================================================================
  function split_clob(
    i_clob      in clob,
    i_separator in varchar2 := ',')
  return m4o_v2tab
  is
    v_tbl        m4o_v2tab   := m4o_v2tab();
    v_start      pls_integer := 1;
    v_sep_length pls_integer := length(i_separator);
    v_sep_pos    pls_integer;
  begin
    IF i_clob is null THEN return v_tbl; END IF;

    LOOP
      v_sep_pos := dbms_lob.instr(i_clob,i_separator,v_start);
      v_tbl.extend;
      IF v_sep_pos = 0 THEN
        v_tbl(v_tbl.count) := dbms_lob.substr(i_clob,4000,v_start);
        exit;
      ELSE
        v_tbl(v_tbl.count) := dbms_lob.substr(i_clob,v_sep_pos-v_start,v_start);
	v_start := v_sep_pos + v_sep_length;
      END IF;
    END LOOP;

    return v_tbl;
  end;
  -- ===========================================================================
  -- Convert
  --   'foo=1:bar=2:baz=3'
  -- to (with i_separator=':' and i_val_sep='=')
  --   m4o_v2.map_t(foo => 1, bar => 2, baz => 3)
  function mapsplit(
    i_str       in varchar2,
    i_separator in varchar2 := ',',
    i_val_sep   in varchar2 := '=' )
  return map_t
  is
    v_map         map_t;
    v_start       pls_integer  := 1;
    v_sep_length  pls_integer  := length(i_separator);
    v_sep2_length pls_integer := length(i_val_sep);
    v_sep_pos     pls_integer;
    procedure append_kv(i_kv_str in varchar2)
    is
      v_sep2_pos pls_integer := instr(i_kv_str,i_val_sep);
    begin
      if v_sep2_pos > 0 then
        v_map(substr(i_kv_str,1,v_sep2_pos-1)) :=
          substr(i_kv_str,v_sep2_pos+v_sep2_length);
      elsif length(i_kv_str) > 0 then
        v_map(i_kv_str) := null;
      end if;
    end;
  begin
    IF i_str is null THEN return v_map; END IF;

    LOOP
      v_sep_pos := instr(i_str,i_separator,v_start);
      IF v_sep_pos = 0 THEN
        append_kv(substr(i_str,v_start));
        exit;
      ELSE
        append_kv(substr(i_str,v_start,v_sep_pos-v_start));
        v_start := v_sep_pos + v_sep_length;
      END IF;
    END LOOP;

    return v_map;
  end;

  -- ===========================================================================
  function join(
    i_tbl  in m4o_v2tab,
    i_glue in varchar2 := ',')
  return varchar2
  is
    v_str varchar2(32767);
  begin
    if i_tbl is not null then
      for i in 1 .. i_tbl.count loop
        v_str := v_str || i_glue || i_tbl(i);
      end loop;
    end if;
    return substr(v_str,length(i_glue)+1);
  end;
  -- ===========================================================================
  function regexp_matches(
    i_str    in varchar2,
    i_regexp in varchar2)
  return m4o_v2tab
  is
    v_separator varchar2(1) := chr(1);
  begin
    return m4o_v2.split(
             regexp_replace(
               regexp_substr(i_str,
                             i_regexp),
               i_regexp,
               '\1'||v_separator||
               '\2'||v_separator||
               '\3'||v_separator||
               '\4'||v_separator||
               '\5'||v_separator||
               '\6'||v_separator||
               '\7'||v_separator||
               '\8'||v_separator||
               '\9'),
            v_separator);
  end;
  -- ===========================================================================
  procedure push(
    i_dest in out nocopy m4o_v2tab,
    i_elem in varchar2)
  is
  begin
    if i_dest is null then
      i_dest := m4o_v2tab(i_elem);
    else
      i_dest.extend;
      i_dest(i_dest.count) := i_elem;
    end if;
  end;
  -- ===========================================================================
  procedure push(
    i_dest in out nocopy varchar2,
    i_elem in varchar2,
    i_glue in varchar2 := ',')
  is
  begin
    if i_dest is null then
      i_dest := i_elem;
    else
      i_dest := i_dest || i_glue || i_elem;
    end if;
  end;
  -- ===========================================================================
  -- append i_elem to i_dest, separate them with i_glue if i_dest is not empty
  procedure push(
    i_dest in out nocopy clob,
    i_elem in varchar2,
    i_glue in varchar2 := ',')
  is
  begin
    if i_dest is null then
      dbms_lob.createtemporary(
        i_dest,
        true);
    end if;

    if i_glue is not null 
       and dbms_lob.getlength(i_dest) > 0 then
      dbms_lob.writeappend(
        i_dest,
        nvl(length(i_elem),0)+length(i_glue),
        i_glue||i_elem);
    elsif i_elem is not null then
      dbms_lob.writeappend(
        i_dest,
        nvl(length(i_elem),0),
        i_elem);
    end if;
  end;
  -- ===========================================================================
  function trim_ws(i_str in varchar2)
  return varchar2
  is
  begin
    return ltrim(rtrim(i_str,WS),WS);
  end;
end;
/

